﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.DependencyInjection.Conditions;
using StyleMVVM.Logging;
#if DOT_NET
using StyleMVVM.Data;

#else
using Windows.Foundation.Metadata;
#endif

namespace StyleMVVM.DependencyInjection.Impl
{
	public sealed class ExportConfiguration : IExportConfiguration
	{
		private static readonly string supplemental = typeof(ExportConfiguration).FullName;
		private readonly List<Type> exportedTypes = new List<Type>();
		private readonly List<string> exportNames = new List<string>();
		private readonly IExportStrategy strategy;
		private CoreExportStartOrder? coreExportStart;
		private readonly List<ImportPropertyInfo> importProperties = new List<ImportPropertyInfo>();
		private readonly List<InitializePropertyInfo> initializeInfos = new List<InitializePropertyInfo>();
		private readonly List<ImportMethodInfo> importMethods = new List<ImportMethodInfo>();
		private readonly List<Tuple<EnrichWithDelegate,int>> enrichWithDelegates = new List<Tuple<EnrichWithDelegate, int>>();
 		private readonly Dictionary<string,object> metadata = new Dictionary<string, object>(); 
		private bool shared;
		private bool permanent;
		private bool cached;
		private bool slidingWindow;
		private TimeSpan cacheExpire;

		public ExportConfiguration(IExportStrategy strategy)
		{
			this.strategy = strategy;
		}

		public IExportConfiguration WithPriority(int priority)
		{
			strategy.Priority = priority;

			return this;
		}

		public IExportConfiguration As(Type exportType)
		{
			if (exportType == null)
			{
				throw new ArgumentNullException("exportType");
			}

			exportedTypes.Add(exportType);

			return this;
		}

		public IExportConfiguration InEnvironment(ExportEnvironment environment)
		{
			strategy.Environment = environment;

			return this;
		}

		public IExportConfiguration AsName(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				throw new ArgumentNullException("name");
			}

			exportNames.Add(name);

			return this;
		}

		public IExportConfiguration AndShared()
		{
			strategy.LifeCycleContainer = new WeakSharedLifeCycleContainer();

			return this;
		}

		public IExportConfiguration AndSharedPermenantly()
		{
			strategy.LifeCycleContainer = new PermanentlySharedLifeCycleContainer();

			return this;
		}

		public IExportConfiguration AndSingleton()
		{
			strategy.LifeCycleContainer = new SingletonLifeCycleContainer();

			return this;
		}

		public IExportConfiguration AndCachedWithAbsoluteExpiration(TimeSpan timeSpan)
		{
			strategy.LifeCycleContainer = new CachedLifeCycleContainer(false, timeSpan);

			return this;
		}

		public IExportConfiguration AndCachedWithSlidingExpiration(TimeSpan timeSpan)
		{
			strategy.LifeCycleContainer = new CachedLifeCycleContainer(true, timeSpan);

			return this;
		}

		public IExportConfiguration ImportDefaultConstructor()
		{
			foreach (ConstructorInfo declaredConstructor in strategy.ActivationType.GetTypeInfo().DeclaredConstructors)
			{
				IInstanceExportStrategy exportStrategy = strategy as IInstanceExportStrategy;

				if (exportStrategy != null)
				{
					exportStrategy.ConstructorInfo = declaredConstructor;
				}

				break;
			}
			

			return this;
		}

		public IExportConfiguration ImportConstructor(object constructorInfo)
		{
			if (constructorInfo == null)
			{
				throw new ArgumentNullException("constructorInfo");
			}

			IInstanceExportStrategy exportStrategy = strategy as IInstanceExportStrategy;

			if (exportStrategy != null)
			{
				exportStrategy.ConstructorInfo = constructorInfo;
			}

			return this;
		}

		public IExportConfiguration CoreExport(CoreExportStartOrder startOrder)
		{
			strategy.IsCoreExport = true;
			strategy.CoreExportStartOrder = (int)startOrder;

			return this;
		}

		public IExportConfiguration Initialize(string propertyName)
		{
			if (string.IsNullOrEmpty(propertyName))
			{
				throw new ArgumentNullException("propertyName");
			}

			return Initialize(propertyName, null);
		}

		public IExportConfiguration Initialize(string propertyName, object initalizeValue)
		{
			if (string.IsNullOrEmpty(propertyName))
			{
				throw new ArgumentNullException("propertyName");
			}

			PropertyInfo propertyInfo =
				strategy.ActivationType.GetTypeInfo().GetDeclaredProperty(propertyName);

			if (propertyInfo != null)
			{
				initializeInfos.Add(
					new InitializePropertyInfo
						{
							PropertyInfo = propertyInfo,
							InitializeValue = initalizeValue
						});
			}
			else
			{
				throw new ArgumentException("propertyName", propertyName + " not found on " + strategy.ActivationType.FullName);
			}

			return this;
		}

		public IExportConfiguration ImportProperty(string propertyName)
		{
			if (string.IsNullOrEmpty(propertyName))
			{
				throw new ArgumentNullException("propertyName");
			}

			return ImportProperty(propertyName, true, null);
		}

		public IExportConfiguration ImportProperty(string propertyName,
		                                           bool isRequired)
		{
			if (string.IsNullOrEmpty(propertyName))
			{
				throw new ArgumentNullException("propertyName");
			}

			return ImportProperty(propertyName, isRequired, null);
		}

		[DefaultOverload]
		public IExportConfiguration ImportProperty(string propertyName,
		                                           bool isRequired,
		                                           string exportName)
		{
			if (string.IsNullOrEmpty(propertyName))
			{
				throw new ArgumentNullException("propertyName");
			}

			PropertyInfo propertyInfo =
				strategy.ActivationType.GetTypeInfo().GetDeclaredProperty(propertyName);

			if (propertyInfo != null)
			{
				importProperties.Add(
					new ImportPropertyInfo
						{
							ExportName = exportName,
							IsRequired = isRequired,
							PropertyInfo = propertyInfo
						});
			}
			else
			{
				throw new ArgumentException("propertyName", propertyName + " not found on " + strategy.ActivationType.FullName);
			}

			return this;
		}

		public IExportConfiguration ImportProperty(string propertyName, IExportActivator exportActivator, bool isRequired)
		{
			if (string.IsNullOrEmpty(propertyName))
			{
				throw new ArgumentNullException("propertyName");
			}

			PropertyInfo propertyInfo =
				strategy.ActivationType.GetTypeInfo().GetDeclaredProperty(propertyName);

			if (propertyInfo != null)
			{
				importProperties.Add(
					new ImportPropertyInfo
						{
							ExportName = "",
							IsRequired = false,
							PropertyInfo = propertyInfo,
							ExportActivator = exportActivator
						});
			}
			else
			{
				throw new ArgumentException("propertyName", propertyName + " not found on " + strategy.ActivationType.FullName);
			}

			return this;
		}

		public IExportConfiguration ImportMethod(string methodName)
		{
			return ImportMethod(methodName, true, null);
		}

		public IExportConfiguration ImportMethod(string methodName, bool isRequired)
		{
			return ImportMethod(methodName, isRequired, null);
		}

		public IExportConfiguration ImportMethod(string methodName, bool isRequired, string exportName)
		{
			importMethods.Add(
				new ImportMethodInfo
					{
						MethodName = methodName
					});

			return this;
		}

		public IExportConfiguration ActivationMethod(string activationMethod)
		{
			if (string.IsNullOrEmpty(activationMethod))
			{
				throw new ArgumentNullException("activationMethod");
			}

			if (strategy is IInstanceExportStrategy)
			{
				((IInstanceExportStrategy)strategy).ActivationMethod = activationMethod;
			}

			return this;
		}

		public IExportConfiguration LifeCycleContainer(ILifeCycleContainer container)
		{
			strategy.LifeCycleContainer = container;

			return this;
		}

		public IExportConfiguration When(ExportConditionDelegate conditionDelegate)
		{
			strategy.AddCondition(new SimpleExportCondition(conditionDelegate));

			return this;
		}

		public IExportConfiguration Unless(ExportConditionDelegate conditionDelegate)
		{
			// invers the delegate
			strategy.AddCondition(new SimpleExportCondition((x, y, z) => !conditionDelegate(x, y, z)));

			return this;
		}

		public IExportConfiguration AndCondition(IExportCondition condition)
		{
			strategy.AddCondition(condition);

			return this;
		}

		public IExportConfiguration AutoWireProperties(bool required)
		{
			Type testType = strategy.ActivationType;

			while (testType != null && testType != typeof(object))
			{
				foreach (PropertyInfo declaredProperty in testType.GetTypeInfo().DeclaredProperties)
				{
					importProperties.Add(
						new ImportPropertyInfo
							{
								PropertyInfo = declaredProperty,
								ExportName = "",
								IsRequired = required
							});
				}

				testType = testType.GetTypeInfo().BaseType;
			}

			return this;
		}

		public IExportConfiguration WithMetadata(string metadataName, object metadataValue)
		{
			metadata[metadataName] = metadataValue;

			return this;
		}

		public IExportConfiguration DisposalCleanupDelegate(BeforeDisposalCleanupDelegate disposalCleanupDelegate)
		{
			strategy.DisposalCleanupDelegate = disposalCleanupDelegate;

			return this;
		}

		public IExportConfiguration EnrichWith(EnrichWithDelegate enrichWithDelegate, int priority)
		{
			enrichWithDelegates.Add(new Tuple<EnrichWithDelegate, int>(enrichWithDelegate,priority));

			return this;
		}

		public IExportStrategy GetExportStrategy()
		{
			if (exportedTypes.Count > 0)
			{
				strategy.ExportedTypes = exportedTypes;

				foreach (Type exportedType in exportedTypes)
				{
					exportNames.Add(exportedType.FullName);
				}
			}

			if (exportNames.Count > 0)
			{
				strategy.ExportNames = exportNames;
			}

			if (strategy.ExportNames == null && strategy.ExportedTypes == null)
			{
				if (strategy.ActivationType != null)
				{
					strategy.ExportNames = new List<string> { strategy.ActivationType.Name };
				}
				else
				{
					throw new Exception("Strategy is not being exported under a type or a name.");
				}
			}

			if (metadata.Count > 0)
			{
				strategy.Metadata = metadata;
			}

			if (enrichWithDelegates.Count > 0)
			{
				strategy.EnrichWithDelegates =
					enrichWithDelegates.OrderByDescending(x => x.Item2).Select(x => x.Item1).ToArray();
			}

			if (strategy is IInstanceExportStrategy)
			{
				IInstanceExportStrategy instanceExport = strategy as IInstanceExportStrategy;

				instanceExport.ImportProperties = importProperties;
				instanceExport.InitializeProperties = initializeInfos;
				instanceExport.ImportMethods = importMethods;
			}

			Logger.Debug(string.Format("Discovered type {0}", strategy.ActivationType.FullName), supplemental);

			return strategy;
		}
	}
}